package personal.zq.practice.common.utils;

import it.sauronsoftware.jave.AudioInfo;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncoderException;
import it.sauronsoftware.jave.MultimediaInfo;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import personal.zq.practice.common.bo.DownloadWithBase64Resp;
import personal.zq.practice.common.bo.FileUtil;
import personal.zq.practice.common.enums.ReturnCodeEnum;

/**
 * @ClassName VideoUtil
 * @Description
 * @Author wangjq
 * @Date 2020/10/26 0026 21:01
 * @Version 1.0
 **/
@Slf4j
public class VideoUtil {

    /**
     * 手动创建线程池
     */
    private static ExecutorService pool = Executors.newCachedThreadPool();

    /**
     * 截取wav音频文件
     *
     * @param start 截取开始时间（豪秒）
     * @param end   截取结束时间（豪秒）
     *              <p>
     *              return  截取成功返回true，否则返回false
     */
    public static File cut(String ffmegPath, DownloadWithBase64Resp downloadWithBase64Resp, String targetfile, int start, int end) {

        //需设置环境变量
        System.setProperty("ffmpeg.home", ffmegPath);
        try {
            if (!"mp3,wav".contains(downloadWithBase64Resp.getFileNameExt().toLowerCase())) {
                throw new BusinessException(ReturnCodeEnum.BUSINESS_ERROR.getCode(), "录音格式有误.");
            }
            if (null == downloadWithBase64Resp.getContent()) {
                throw new BusinessException(ReturnCodeEnum.BUSINESS_ERROR.getCode(), "文件不存在.");
            }
            if (start < 0 || end <= 0) {
                log.error("截取失败：start:{}|end:{}", start, end, "截取时间有误.");
                return null;
            }
            //1.先转化格式为wav
            byte[] sourceBytes = downloadWithBase64Resp.getContent();
            if ("mp3".equals(downloadWithBase64Resp.getFileNameExt().toLowerCase())) {
                sourceBytes = mp3ToWav(targetfile, ffmegPath, downloadWithBase64Resp.getContent());
            }

            return ffmegCut(ffmegPath, sourceBytes, Long.valueOf(start), Long.valueOf(end), targetfile);

        } catch (Exception e) {
            log.error("剪切录音异常：start:{}|end:{},exp:{}", start, end, e);
            return null;
        }
    }


    public static File ffmegCut(String basePath, byte[] content, Long startTime, Long endTime, String targetDir) {
        String target = targetDir + "/cut/";
        String start = getFormatTime(startTime);
        String end = getFormatTime(endTime);

        String wavName = UUID.randomUUID().toString().replaceAll("-", "") + ".wav";
        String targetName = UUID.randomUUID().toString().replaceAll("-", "") + ".wav";

        new File(target + targetName);
        try {
            if (VideoUtil.save(target, wavName, content)) {
                String cutCommand = MessageFormat.format("{0} -i {1} -vn -acodec copy -ss {2} -to {3} {4}", basePath, target + wavName, start, end,
                        target + targetName);
                Process process = Runtime.getRuntime().exec(cutCommand);
                process.waitFor();
                VideoUtil.getFile(target, targetName);
                return new File(target, targetName);
            }
        } catch (Exception e) {
            log.error("剪切：{}", e);

        }
        return null;
    }

    /**
     * 获取音频文件总时长 (毫秒)
     *
     * @return
     */
    public static long getTimeLen(File file) {
        long tlen = 0;
        if (file != null && file.exists()) {
            Encoder encoder = new Encoder();
            try {
                MultimediaInfo m = encoder.getInfo(file);
                long ls = m.getDuration();
                tlen = ls;
            } catch (Exception e) {
                log.error("异常", e);
            }
        }
        return tlen;
    }

    /**
     * 数组反转
     *
     * @param array
     */
    public static byte[] reverse(byte[] array) {
        byte temp;
        int len = array.length;
        for (int i = 0; i < len / 2; i++) {
            temp = array[i];
            array[i] = array[len - 1 - i];
            array[len - 1 - i] = temp;
        }
        return array;
    }


    /**
     * 录音文件转换
     *
     * @param ffmpegPath ffmpeg 环境
     * @param data       源文件二进制文件
     * @return 返回wav 文件内容二进制数据
     */
    public static byte[] mp3ToWav(String basePath, String ffmpegPath, byte[] data) {
        byte[] body = null;
        String mp3Name = UUID.randomUUID().toString().replaceAll("-", "") + ".mp3";
        String wavName = UUID.randomUUID().toString().replaceAll("-", "") + ".wav";
        log.info("mp3FileName=={}，wavFileName=={}", mp3Name, wavName);
        Future<Integer> executeFuture = null;
        Process process = null;
        try {
            if (VideoUtil.save(basePath, mp3Name, data)) {
                if (StringUtils.isNotBlank(ffmpegPath)) {
                    if (!PathPatternUtil.judgeRoute(ffmpegPath)) {
                        throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "指定ffmpeg路径地址有误");
                    }
                }
                if (StringUtils.isNotBlank(basePath)) {
                    if (!PathPatternUtil.judgeRoute(basePath)) {
                        throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "指定音频文件保存路径地址有误");
                    }
                }
                // 将mp3转换成wav 采样率16000 16kbs wav音频文件
                String command = MessageFormat.format("{0} -i {1} -ar 8000 -acodec pcm_s16le {2}", ffmpegPath, basePath + mp3Name,
                        basePath + wavName);
                process = Runtime.getRuntime().exec(command);
                final Process p = process;
                Callable<Integer> call = () -> {
                    p.waitFor();
                    return p.exitValue();
                };
                //使用线程池防止Process阻塞
                executeFuture = pool.submit(call);
                Integer result = executeFuture.get(1000, TimeUnit.MILLISECONDS);
                // 获取文件流
                if (result == 0) {
                    body = VideoUtil.getFile(basePath, wavName);
                }
            }
        } catch (Exception e) {
            log.error("mp3转wav失败", ExceptionUtils.getStackTrace(e));
        } finally {
            FileUtil.deleteFile(basePath + mp3Name);
            FileUtil.deleteFile(basePath + wavName);
            //关闭线程池线程，以及销毁调用处理线程
            if (executeFuture != null) {
                try {
                    executeFuture.cancel(true);
                } catch (Exception ignore) {
                }
            }
            if (process != null) {
                process.destroy();
            }
        }
        return body;
    }


    /**
     * ffmpeg -i input.wav -f s16be -ar 8000 -ac 1 -acodec pcm_s16be output.pcm
     * 使用ffmpeg 工具将wav 转pcm 保存
     *
     * @param wavDate        wav文件内容
     * @param ffmpegHomePath ffmpeg 安装路径
     */
    public static byte[] ffmpegWav2Pcm(String basePath, String ffmpegHomePath, byte[] wavDate) {
        byte[] body = null;
        if (!basePath.endsWith(File.separator)) {
            basePath += File.separator;
        }
        Future<Integer> executeFuture = null;
        Process process = null;
        String wavName = UUID.randomUUID().toString().replaceAll("-", "") + ".wav";
        String pcmName = UUID.randomUUID().toString().replaceAll("-", "") + ".pcm";
        try {
            FileUtil.uploadFile(wavDate, basePath, wavName);
            //校验上传文件是否合法，须先保存文件
            validateWav(basePath + wavName);
            if (StringUtils.isNotBlank(ffmpegHomePath)) {
                if (!PathPatternUtil.judgeRoute(ffmpegHomePath)) {
                    throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "指定ffmpeg路径地址有误");
                }
            }
            if (StringUtils.isNotBlank(basePath)) {
                if (!PathPatternUtil.judgeRoute(basePath)) {
                    throw new BusinessException(ReturnCodeEnum.SYSTEM_ERROR, "指定音频文件保存路径地址有误");
                }
            }
            // 将mp3转换成wav 采样率16000 16kbs wav音频文件
            String command = MessageFormat.format("{0} -i {1} -f s16be -ar 16000 -acodec pcm_s16le {2}", ffmpegHomePath, basePath + wavName,
                    basePath + pcmName);
            process = Runtime.getRuntime().exec(command);
            final Process p = process;
            Callable<Integer> call = () -> {
                p.waitFor();
                return p.exitValue();
            };
            //使用线程池防止Process阻塞
            executeFuture = pool.submit(call);
            Integer result = executeFuture.get(1000, TimeUnit.MILLISECONDS);
            // 获取文件流
            if (result == 0) {
                body = FileUtil.fileToBytes(basePath + pcmName);
            }

        } catch (BusinessException businessException) {
            throw businessException;
        } catch (Exception e) {
            log.error("wav文件转换失败:{}", e);
            throw new BusinessException(ReturnCodeEnum.ERROR_4007, "wav文件转换失败");
        } finally {
            FileUtil.deleteFile(basePath + wavName);
            FileUtil.deleteFile(basePath + pcmName);
            //关闭线程池线程，以及销毁调用处理线程
            if (executeFuture != null) {
                try {
                    executeFuture.cancel(true);
                } catch (Exception ignore) {
                }
            }
            if (process != null) {
                process.destroy();
            }
        }
        if (body == null) {
            throw new BusinessException(ReturnCodeEnum.ERROR_4007, "wav文件转换失败");
        }
        return body;
    }

    /**
     * 保存文件
     *
     * @param filePath 文件夹地址
     * @param fileName 文件名
     * @param content  文件内容二进制内容
     * @return 返回是否保存成功
     */
    public static boolean save(String filePath, String fileName, byte[] content) {
        File fileDir = new File(filePath);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        File file = new File(fileDir, fileName);
        try (FileOutputStream os = new FileOutputStream(file)) {
            os.write(content, 0, content.length);
            os.flush();
        } catch (IOException e) {
            log.error("保存文件流异常，函数名称：save，fileName：{},filePath：{}，异常原因：{}", fileName, filePath, ExceptionUtils.getStackTrace(e));
            return false;
        }
        return true;
    }

    /**
     * 获取文件信息
     *
     * @param filePath 文件路径
     * @param fileName 文件名
     * @return 返回文件二进制数据
     */
    public static byte[] getFile(String filePath, String fileName) {
        byte[] buffer = null;
        File file = new File(filePath, fileName);
        try (FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] b = new byte[1024 * 4];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            buffer = bos.toByteArray();
        } catch (Exception e) {
            log.error("获取文件流异常，函数名称：getFile，fileName：{},filePath：{}，异常原因：{}", fileName, filePath, ExceptionUtils.getStackTrace(e));
        }
        return buffer;
    }

    public static byte[] getFile(String fileName) {
        byte[] buffer = null;
        try (FileInputStream fis = new FileInputStream(new File(fileName)); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] b = new byte[1024 * 4];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            buffer = bos.toByteArray();
        } catch (Exception e) {
            log.error("获取文件流异常，函数名称：getFile，fileName：{},filePath：{}，异常原因：{}", fileName, ExceptionUtils.getStackTrace(e));
        }
        return buffer;
    }

    public static String getGapTime(long time) {
        long hours = time / (1000 * 60 * 60);
        long minutes = (time - hours * (1000 * 60 * 60)) / (1000 * 60);
        long second = (time - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) / 1000;
        long mill = (time - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) % 1000;
        String diffTime = "";
        if (minutes < 10) {
            diffTime = hours + ":0" + minutes;
        } else {
            diffTime = hours + ":" + minutes;
        }
        if (second < 10) {
            diffTime = diffTime + ":0" + second + "." + mill;
        } else {
            diffTime = diffTime + ":" + second + "." + mill;
        }
        return diffTime;
    }

    public static String getFormatTime(long time) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss:SSS");
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+0:00"));
        Date date = new Date(time);

        return simpleDateFormat.format(date);
    }

    /**
     * 校验wav音频格式
     *
     * @param fileName 文件路径
     */
    private static void validateWav(String fileName) throws EncoderException {
        //标准码率
        int standardBit = 256;
        //标准采样率
        int standardSamplingRate = 16000;
        //过去空格，以及校验输入是否符合规则
        fileName = fileName.trim();
        if (!PathPatternUtil.judgeRoute(fileName)) {
            throw new BusinessException(ReturnCodeEnum.ERROR_108, "文件路径错误");
        }
        File wav = new File(fileName);
        Encoder encoder = new Encoder();
        MultimediaInfo mul = encoder.getInfo(wav);
        AudioInfo audio = mul.getAudio();
        //码率
        int bit = audio.getBitRate();
        // 采样率
        int samplingRate = audio.getSamplingRate();
        if (standardBit != bit) {
            throw new BusinessException(ReturnCodeEnum.ERROR_108, "wav文件码率非法");
        }
        if (standardSamplingRate != samplingRate) {
            throw new BusinessException(ReturnCodeEnum.ERROR_108, "wav文件采样率非法");
        }
    }
}
